package br.ufrj.ppgi.infovis.mtgraph;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;

import org.mt4j.components.MTComponent;
import org.mt4j.components.TransformSpace;
import org.mt4j.sceneManagement.IPreDrawAction;
import org.mt4j.util.MT4jSettings;
import org.mt4j.util.math.Vector3D;


/**
 * @author Rodrigo Andrade
 *  
 * A graph object contains nodes and handles
 * some administration/data about it for easy/quick access.
 * 
 */

public class Graph {	
	
	private GraphScene graphScene = null;
	
	private List<GraphNode> nodeList;
	private List<GraphEdge> edgeList;
	
	private Map<String,GraphNode> nodeMap;
	
	/*
	 * The update system will keep track whether a modification is needed or not.
	 * True: something has changed, the tree needs an update.
	 * False: the tree has not changed. It needs no update.
	 */
	
	private boolean invalidated = false;
	
	private boolean running = false;

	private Updater preUpdate;
	
	/**
	 * Constructor for an empty tree, no nodes set.
	 */
	public Graph() {
		preUpdate = new Updater();
		
		nodeList = new ArrayList<GraphNode>();
		edgeList = new ArrayList<GraphEdge>();		
		nodeMap = new HashMap<String, GraphNode>();
		
	}
	
	public String generateNewNodeId(){
		
		Set<String> keys = nodeMap.keySet();
		int maxid=-1;
		for (String key : keys) {
			int id = Integer.parseInt(key);
			if (id > maxid) maxid = id;
		}
		maxid++;
		
		return ((Integer) maxid).toString();

	}
	
	public void addNode(GraphNode node) {
		nodeList.add(node);
		nodeMap.put(node.getId(), node);
	}
	

	public void addEdge(GraphEdge edge) {
		edgeList.add(edge);
	}
	
	public void removeEdge(GraphEdge edge) {
		edgeList.remove(edge);
		this.invalidate();
	}
		
	public int countNodes() {
		return nodeList.size();
	}


	/**
	 * Return an array with all the nodes in the tree.
	 * 
	 * When includeInvisible is set to false, this method will not count
	 * nodes where isVisible() evaluates to false.
	 * 
	 * @param includeInvisible whether to include hidden nodes or not
	 * @return an array containing all the nodes
	 */
	
	public GraphNode[] getNodeList() {
		
		return nodeList.toArray(new GraphNode[0]);
		
	}

	public GraphEdge[] getEdgeList() {
		return edgeList.toArray(new GraphEdge[0]);
	}
	
	/**
	 * Iterate through the tree using a NodeProcessor.
	 * 
	 * The root will be the first node to be processed,
	 * and parents will be processed before their children.
	 * 
	 * Other than that, there should be no assumptions about the order
	 * in which nodes are processed.
	 * 
	 * @param nodeProcessor the NodeProcessor instance to use
	 */
	public void processGraph(GraphNodeProcessor nodeProcessor) {
		for (GraphNode n : nodeList) {
			nodeProcessor.processNode(n);
		}

	}
	
	

	/*
	 * Some administrative stuff...
	 */

	/*
	 * 
	 * Note: this does the decoupling from existing scene
	 * Note: this does not automatically cause an update,
	 * so you can attach your own (non-default) components to Nodes
	 * before the update would attach defaults.
	 */
	/**
	 * Attach this tree to a TreeScene object.
	 * 
	 * This also handles decoupling from existing scene.
	 * 
	 * Note that this does not automatically cause an update,
	 * so you can attach your own (non-default) components to Nodes
	 * before the update would attach defaults.
	 * 
	 * @param set the TreeScen- Funcionalidade de Exportar os nós e arestas;
- Ao criar novos nós verificar qual o ultimo id utilizado e e instance to attach this Tree to
	 */
	public void setGraphScene(GraphScene set) {
		
		if (graphScene == null) {
			// No existing tree thing
			// Invalidate, and possibly auto-update
			graphScene = set;
			graphScene.setGraph(this);
			graphScene.registerPreDrawAction(preUpdate);
			invalidate();
		} else if (set == null) {
			// Decouple tree from scene
			for (GraphNode node : nodeList) {
				node.setComponent(null);
			}
			// Remove everything from tree scene, including lines.
			graphScene.removeAllNodeComponents();
			graphScene.setGraph(null);
			graphScene.unregisterPreDrawAction(preUpdate);
			graphScene = set;

			invalidate();
			// Invalidate?
		} else {
			setGraphScene(null);
			setGraphScene(set);
		}
	}
	

	public GraphScene getGraphScene() {
		return graphScene;
	}


	/**
	 * Return whether this Tree needs an update.
	 * 
	 * When the structure of the Tree changes, or when nodes
	 * are switched between hidden or visible, the Tree is invalidated.
	 * 
	 * The invalidation is lifted when the system has finished a visual update.
	 * 
	 * 
	 * @return whether this tree needs an update or not
	 */
	public boolean needsUpdate() {
		return invalidated;
	}


	/**
	 * Update tree administration and visibility, and lift the invalidation.
	 * 
	 * This method is to be called after structural changes to the Tree are done.
	 * 
	 * When this method is called, visible nodes will be placed on the scene,
	 * invisible nodes will be hidden, and the placement of nodes will be
	 * recalculated.
	 * Nodes that have no component are given a default
	 * component (TreeScene.makeDefaultComponent())
	 * 
	 * Applications can generate or modify a Tree structure, but the changes are not
	 * complete until this method is called.
	 * This is done to prevent the framework from internally calling update routines
	 * on each single change in structure.
	 */
	/*public void update() {
	}*/
	
	private void update() {
		if (graphScene == null) return;
		//if (root == null) return;

		// Take all currently shown nodes and remove nodes we shouldn't see
		GraphNode[] nodelist = graphScene.getAllNodes();
		
		for (GraphNode n: nodelist)
			// If it is not in our tree, or if it's hidden
			if (n.getGraph() != this)
				graphScene.removeChild(n.getComponent());
		
		
		// Now grab all the nodes we wish to show
		nodelist = getNodeList();
		
		
		
		// This is an O(n) operation
		for (GraphNode n: nodelist) {
			if (n.getComponent() == null)
				n.setComponent();
			
			MTComponent parent = n.getComponent().getParent();
			if (parent != null) {
				System.out.println("node parent: " + parent.getClass());
			}
			graphScene.addNode(n);

		}

		
		
		nodePlacements(); // Recalculate node placements
		
		
		GraphEdge[]  edgelist = this.getEdgeList();
		
		for (GraphEdge e : edgelist) {
			if (e.getComponent() == null)
				e.setComponent();
			

			MTComponent parent = e.getComponent().getParent();
			if (parent != null) {
				System.out.println("edge parent: " + parent.getClass());
			}
			graphScene.addEdge(e);
		}
		
		graphScene.invalidate();
		
		invalidated = false;
		running = true;
		
	}
	
	/**
	 * Internal. Invalidate the tree structure.
	 */
	protected void invalidate() {
		invalidated = true;
		if (graphScene != null)
			graphScene.invalidate();
	}

	/**
	 * Calculates the placement of nodes.
	 * This changes the position of the Nodes in the tree.
	 * 
	 * Part of the update method.
	 *  
	 */
	private void nodePlacements() {
		// Go through entire tree and determine node placements
		GraphNode[] list = getNodeList();
		int n = list.length;
		/*
		float margin = 50;
		float width = MT4jSettings.getInstance().getWindowWidth();
		float height = MT4jSettings.getInstance().getWindowHeight();
		int ncol = (int) Math.ceil(Math.sqrt(n)); 
		int nrow = (int) Math.floor(Math.sqrt(n));
		
		float delta_x = (width - 2*margin) / ncol;
		float delta_y = (height - 2*margin) / nrow;
		
		float x_inic = margin + delta_x/2;
		float y_inic = margin + delta_y/2;
		
		for (int k = 0; k < n ; k++) {
			float xi = x_inic + (k % ncol)*delta_x;
			float yi = y_inic + (k / ncol)*delta_y;
			list[k].getComponent().setPositionGlobal(new Vector3D(xi, yi, 0));
		}
		*/
		
		Vector canvas_places = GraphScene.canvas_places;
		for (int k = 0; k < n ; k++) {
			double random = Math.random();
			int pos = (int) ((canvas_places.size()-1) * random);
			Vector3D vetor_pos = (Vector3D) canvas_places.elementAt(pos);
			list[k].getComponent().setPositionGlobal(vetor_pos);
			canvas_places.remove(pos);
			if (canvas_places.size() == 0) { break; }
		}

	}
	
	private class Updater implements IPreDrawAction {
		
		@Override
		public boolean isLoop() {
			// TODO Auto-generated method stub
			return true;
		}

		@Override
		public void processAction() {
			// TODO Auto-generated method stub
			if (nodeList.size() > 0 && invalidated && !running) update();
			//if (nodeList.size() > 0 && invalidated ) update();
		}
		
	}
	
	public GraphNode getNodeById(String id) {
		return nodeMap.get(id);
	}
	
	public void removeNode(GraphNode node) {
		
		// remove all adjacent edges 
		
		for (AdjacentEntry ae : node.getAllAdjacentNodes()) {
			GraphEdgeManager.removeGraphEdge(ae.getEdge());
		}

		// first, remove node from map and list
		nodeMap.remove(node);
		nodeList.remove(node);

		
	}
	
}
